/**
 * @file   MiraCSIndexCont.h
 * @author Josep Sabater (jsabaterm@el.ub.edu)
 * @date   July, 2016
 * @version 1.0.0
 * @ingroup MiraUtilUBLib
 * @brief Indexed container with constant size (runtime bound).
 * This is a constant time access container to store data.
 * Its size is determined when created and is constant along
 * its life.
 * 
 * Similar to dynamic arrays for the C++14 Standard
 *
 */

#ifndef MIRA_CONST_SIZE_INDEX_CONT_H
#define MIRA_CONST_SIZE_INDEX_CONT_H

#include <cstddef>        // ptrdiff_t
#include <limits>         // std::numeric_limits 
#include <stdexcept>      // std::out_of_range
#include <memory>         // std::uninitialized_copy 
#include <new>            // std::bad_array_new_length  

namespace MiraUtilUBLib{

template< class T >
    struct CSIndexCont{
        // types:
        typedef       T                               value_type;
        typedef       T&                              reference;
        typedef const T&                              const_reference;
        typedef       T*                              iterator;
        typedef const T*                              const_iterator;
        typedef std::reverse_iterator<iterator>       reverse_iterator;
        typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
        typedef size_t                                size_type;
        typedef ptrdiff_t                             difference_type;

        // fields:
    private:
        T*        store;
        size_type count;

        // helper functions:
        void check(size_type n)
            { if ( n >= count ) throw std::out_of_range("CSIndexCont"); }
        
        T* alloc(size_type n)
            { if ( n > std::numeric_limits<size_type>::max()/sizeof(T) )
                  throw std::length_error("CSIndexCont");
              return reinterpret_cast<T*>( new char[ n*sizeof(T) ] ); }        

    public:
        // construct and destruct:
        CSIndexCont() = delete;
        const CSIndexCont operator=(const CSIndexCont&) = delete;

        explicit CSIndexCont(size_type c)
            : store( alloc( c ) ), count( c )
            { size_type i;
              try {
                  for ( size_type i = 0; i < count; ++i )
                      new (store+i) T;
              } catch ( ... ) {
                  for ( ; i > 0; --i )
                     (store+(i-1))->~T();
                  throw;
              } 
            }
        
        explicit CSIndexCont(size_type c, const T& t)
            : store( alloc( c ) ), count( c )
            { size_type i;
              try {
                  for ( size_type i = 0; i < count; ++i )
                      new (store+i) T(t);
              } catch ( ... ) {
                  for ( ; i > 0; --i )
                     (store+(i-1))->~T();
                  throw;
              } 
            }        

        CSIndexCont(const CSIndexCont& d)
            : store( alloc( d.count ) ), count( d.count )
            { try { std::uninitialized_copy( d.begin(), d.end(), begin() ); }
              catch ( ... ) { delete store; throw; } }

        ~CSIndexCont()
            { for ( size_type i = 0; i < count; ++i )
                  (store+i)->~T();
              delete[] store; }

        // iterators:
        iterator       begin()        { return store; }
        const_iterator begin()  const { return store; }
        const_iterator cbegin() const { return store; }
        iterator       end()          { return store + count; }
        const_iterator end()    const { return store + count; }
        const_iterator cend()   const { return store + count; }

        reverse_iterator       rbegin()       
            { return reverse_iterator(end()); }
        const_reverse_iterator rbegin()  const
            { return reverse_iterator(end()); }
        reverse_iterator       rend()         
            { return reverse_iterator(begin()); }
        const_reverse_iterator rend()    const
            { return reverse_iterator(begin()); }

        // capacity:
        size_type size()     const { return count; }
        size_type max_size() const { return count; }
        bool      empty()    const { return count == 0; }

        // element access:
        reference       operator[](size_type n)       { return at(n); }
        const_reference operator[](size_type n) const { return at(n); }

        reference       front()       { check(0); return store[0]; }
        const_reference front() const { check(0); return store[0]; }
        reference       back()        { check(count-1); return store[count-1]; }
        const_reference back()  const { check(count-1); return store[count-1]; }

        const_reference at(size_type n) const { check(n); return store[n]; }
        reference       at(size_type n)       { check(n); return store[n]; }
        

        // data access:
        T*       data()       { return store; }
        const T* data() const { return store; }
    };

}
#endif /* MIRA_CONST_SIZE_INDEX_CONT_H */

